package com.ymh.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * Zookeeper实现分布式锁
 * Created by aivin on 2018/7/27.
 */
public class ZookeeperImproveLock implements Lock {

    private static Logger logger = LoggerFactory.getLogger(ZookeeperImproveLock.class);


    private static final String LOCK_PATH = "/LOCK";

    private static final String ZOOKEEPER_IP_PORT = "localhost:2181";

    private ZkClient zkclient = new ZkClient(ZOOKEEPER_IP_PORT, 1000, 1000, new SerializableSerializer());

    private CountDownLatch countDownLatch;

    private String beforePath;//当前请求的节点前一个节点
    private String currentPath;//当前请求的节点

    /**
     * 通过构造函数初始化LOCK目录，没有则创建
     */
    public ZookeeperImproveLock() {
        if (!this.zkclient.exists(LOCK_PATH)) {
            this.zkclient.createPersistent(LOCK_PATH);
        }
    }

    //阻塞式加锁
    @Override
    public void lock() {
        //尝试加锁
        if (tryLock()) {
            //加锁成功
            return;
        }
        //如果没加上阻塞当前线程
        waitForLock();
        //递归调用再尝试加锁
    }

    //阻塞当前线程
    private void waitForLock() {
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                //捕获到前置节点被删除的事件以后，发令枪countdown，让主线程停止等待
                logger.info(Thread.currentThread().getName() + ":捕获到DataDelete事件！-----------------");
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };
        //给排在前面的节点增加数据删除的watcher，本质是启动另外一个线程去监听前置节点
        this.zkclient.subscribeDataChanges(beforePath, listener);

        if (this.zkclient.exists(beforePath)) {
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.zkclient.unsubscribeDataChanges(beforePath, listener);

    }

    //非阻塞式加锁
    @Override
    public boolean tryLock() {
        //如果currentPath为空则为第一次尝试加锁，第一次加锁赋值currentPath
        if (currentPath == null || currentPath.length() <= 0) {
            //创建一个临时顺序节点
            currentPath = this.zkclient.createEphemeralSequential(LOCK_PATH + '/', "lock");
            System.out.println("----------------------->" + currentPath);
        }
        //获取所有临时节点并排序，临时节点名称为自增长的字符串，例如：00000000400
        List<String> childrens = this.zkclient.getChildren(LOCK_PATH);
        Collections.sort(childrens);

        if (currentPath.equals(LOCK_PATH + ',' + childrens.get(0))) {
            //如果当前节点在所有节点中排名第一则获取锁成功
            return true;
        } else {
            //如果当前节点在所有节点排名中不是排名第一，则获取前面的节点名称，并赋值给beforePath
            int wz = Collections.binarySearch(childrens, currentPath.substring(6));
            beforePath = LOCK_PATH + '/' + childrens.get(wz - 1);
        }
        return false;
    }

    @Override
    public void unlock() {
        //删除当前临时节点
        zkclient.delete(currentPath);
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


}
